#!/usr/bin/python
#***************************************************************************
# Copyright notice for this file:
#  Copyright (C) 2012 TheAxeMan
#  _______ _                               _    _
# |__   __| |     ___     /\          ___ | \  / |
#    | |  | |__  / _ \   /  \  __  _ / _ \|  \/  | __ _ _ __
#    | |  | '_ \|  __/  / /\ \ \ \/ |  __/| .  . |/ _` | '_ \
#    | |  | | | | (__/\/ ____ \ )  (| (__/| |\/| | (_| | | | |
#    |_|  |_| |_|\____/_/    \_._/\_.\____|_|  |_|\__,_|_| |_|
#
# Final Fantasy TAS scripts by TheAxeMan
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#***************************************************************************


from array import array


class CharType:
  """Really just a namespace for character types"""
  garland = 1
  pirate = 2
  wizard = 3
  level1fighter = 80
  level2fighter = 81
  level3fighter = 82
  level1whitemage = 83
  level2whitemage = 84
  level3whitemage = 85


class BattleRandGenerator:
  #this list was pulled from emulator memory viewer
  temp_rand_list = """
   AE D0 38 8A ED 60 DB 72 5C 59 27 D8 0A 4A F4
34 08 A9 C3 96 56 3B F1 55 F8 6B 31 EF 6D 28 AC
41 68 1E 2A C1 E5 8F 50 F5 3E 7B B7 4C 14 39 12
CD B2 62 8B 82 3C BA 63 85 3A 17 B8 2E B5 BE 20
CB 46 51 2C CF 03 78 53 97 06 69 EB 77 86 E6 EA
74 0C 21 E2 40 D4 5A 3D C7 2B 94 D5 8C 44 FD EE
D2 43 00 BB FA C6 1D 98 A0 D3 54 5F 5E DC A8 00
AF 93 A1 E1 6C 04 DE B6 D7 36 16 C5 C8 C4 E4 0F
02 AB E8 33 99 73 11 6A 09 67 F3 FF A2 DF 32 0E
1F 0D 90 25 64 75 B3 65 2F C9 B0 DA 5D 9F EC 29
CE E3 F0 91 7A 58 45 24 1C 47 A4 89 18 2D CC BD
6F 80 F6 81 22 E9 07 70 FB DD AD 35 A6 61 B4 A3
FE B1 30 4B 15 48 6E 4F 5B 13 9C 83 92 01 C2 19
7F 1A 1B 71 B9 3F 4E 9B BF 9E 87 0B 10 57 F2 26
79 9A 05 C0 E0 F7 4D 7D CA 52 9D F9 BC AA FC 8D
7E D1 A5 42 E7 D6 76 A7 84 8E 66 7C 23 88 37 49
D9
""".split()
  rand_list = array('i', [int(t, 16) for t in temp_rand_list])
    
  __slots__ = ('seed',)

  def __init__(self):
    self.seed = 0

  def copy(self):
    c = BattleRandGenerator()
    c.seed = self.seed
    return c

  def getNextRand(self):
    """Get the next rand from the list and increment seed."""
    r = BattleRandGenerator.rand_list[self.seed]
    if self.seed >= 255:
      self.seed = 0
    else:
      self.seed += 1
    return r

  def useUpRands(self, num):
    """Equivalent to getting and throwing away a number of rands."""
    self.seed = (self.seed + num) % 256

  def getRandInRange(self, min, max):
    """Returns a rand in the range min to max."""
    r = self.getNextRand()
    rand_range = 1 + max - min
    return min + (r * rand_range) / 256
  
  def getTarget(self):
    """Returns target picked by an enemy"""
    s = self.seed
    self.useUpRands(1)
    return BattleRandGenerator.targetLookupTable[s]
      
  @classmethod
  def populateTargetLookupTable(cls):
    rand = BattleRandGenerator()
    cls.targetLookupTable = array('i', range(256))
    for s in xrange(256):
      rand.seed = s
      target_roll = rand.getNextRand()
      if target_roll <= 32:
        target_hero = 3
      elif target_roll < 64:
        target_hero = 2
      elif target_roll < 128:
        target_hero = 1
      else:
        target_hero = 0
      cls.targetLookupTable[s] = target_hero

  def getBattleOrder(self):
    """Use the randomization engine provided to get the battle order"""
    battle_order = BattleRandGenerator.battleOrderLookupTable[self.seed]
    self.useUpRands(34)
    return battle_order
      
  @classmethod
  def populateBattleOrderLookupTable(cls):
    cls.battleOrderLookupTable = range(256)
    rand = BattleRandGenerator()
    for s in xrange(256):
      rand.seed = s
      #start out like this, with enemies as 0-8 and party 80-83
      battle_order = [0,1,2,3,4,5,6,7,8,80,81,82,83]
      for i in xrange(17):
        swap_a = rand.getRandInRange(0,12)
        swap_b = rand.getRandInRange(0,12)
        temp = battle_order[swap_a]
        battle_order[swap_a] = battle_order[swap_b]
        battle_order[swap_b] = temp
      cls.battleOrderLookupTable[s] = tuple(battle_order)

  def luckRaiseSeeds(self):
    """Returns a list of seeds that raise luck on levelup.
       Note that this is the seed that has to come up on the third
       roll in the levelup sequence. (for garland)
    """
    lucky_seeds = []
    for i in xrange(256):
      if BattleRandGenerator.rand_list[i] % 4 == 0:
        lucky_seeds.append(i)
    return lucky_seeds
  

class Combatant:
  """Could be a monster or hero!"""
  damage = 11
  defense = 0
  num_hits = 1
  crit_threshold = 0
  hit_threshold = 142
  type = 0
  __slots__ = ('hp',)
  
  def __init__(self, initHp=28):
    self.hp = initHp

  def copy(self):
    return self.__class__(self.hp)

  def takeDamage(self, damage):
    """Update hp and status after taking damage"""
    self.hp -= min(self.hp, damage)

  def attack(self, target, rand_engine, print_results=False):
    """This combatant attacks another. Damage is computed, rand engine
       updated and target takes damage."""
    lookup = rand_engine.seed
    lookupTable = self.__class__.attackDamageLookupTable
    total_damage = lookupTable[lookup]
    if self.num_hits == 1:
      if rand_engine.seed == 138:
        rand_engine.useUpRands(1)
      else:
        rand_engine.useUpRands(2)
    else:
      for h in xrange(self.num_hits):
        if rand_engine.seed == 138:
          rand_engine.useUpRands(1)
        else:
          rand_engine.useUpRands(2)
    
    if print_results:
      if total_damage == 0:
        print "missed!"
      else:
        print total_damage, "damage"

    target.takeDamage(total_damage)
    return total_damage
  
  @classmethod
  def populateDamageLookupTable(cls, target):
    cls.attackDamageLookupTable = array('i', range(256))
    rand = BattleRandGenerator()
    for s in xrange(256):
      rand.seed = s
      total_damage = 0
      for i in xrange(cls.num_hits):
        hit_roll = rand.getRandInRange(0, 200)
        damage_roll = rand.getRandInRange(0, cls.damage)
        if hit_roll > cls.hit_threshold:
          # missed!
          continue
        unmodified_damage = damage_roll + cls.damage
        hit_damage = unmodified_damage - target.defense
        if hit_damage < 1:
          hit_damage = 1
        if hit_roll <= cls.crit_threshold:
          hit_damage += unmodified_damage
        total_damage += hit_damage
      cls.attackDamageLookupTable[s] = total_damage

      
def rand_test():
  BattleRandGenerator.populateBattleOrderLookupTable()
  BattleRandGenerator.populateTargetLookupTable()
  rand_engine = BattleRandGenerator()
  
  #print "rand list has",len(rand_engine.rand_list),"entries"
  #print rand_engine.rand_list

  #rl_copy = list(BattleRandGenerator.rand_list)
  #rl_copy.sort()
  #print "sorted:",rl_copy
  
  c = Combatant()
  t = Combatant()
  Combatant.populateDamageLookupTable(t)

  if False:
    damages = []
    for x in xrange(256):
      rand_engine.seed = x
      damage = c.attack(t, rand_engine)
      damages.append(damage)
    print sorted(damages)
    
  elif False:
    for x in xrange(100):
      print rand_engine.getBattleOrder()
    
  else:
    counts = [0,0,0,0]
    for x in xrange(256):
      rand_engine.seed = x
      target = rand_engine.getTarget()
      counts[target] += 1
    print counts
    
  #print rand_engine.luckRaiseSeeds()
  for i,r in enumerate(rand_engine.rand_list):
    print i,":",r
  
if __name__ == '__main__':
  rand_test()

